/****************************************************************************
 * Copyright (C) 2009-2010 SciTouch LLC
 * 
 * This file is part of Indigo toolkit.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.GPL included in the
 * packaging of this file.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

//
// Check wherever aromatic query can match some molecule
//

#include "molecule/molecule_arom_match.h"
#include "molecule/molecule_dearom.h"

AromaticityMatcher::AromaticityMatcher (const Molecule &query, const Molecule &base) :
   _query(query), _base(base), _needPrepare(true),
   TL_CP_GET(_edgesMatching),
   TL_CP_GET(_verticesFilter),
   TL_CP_GET(_submolecule)
{
   _edgesMatching.clear();
   _verticesFilter.clear();

   validateQuery();
   _query_vertices_filter = 0;
   _ignore_value = 0;
}

void AromaticityMatcher::validateQuery ()
{
   int old_size = _edgesMatching.size();
   _edgesMatching.resize(_query.edgeEnd());
   for (int i = old_size; i < _query.edgeEnd(); i++)
      _edgesMatching[i] = 0;

   old_size = _verticesFilter.size();
   _verticesFilter.resize(_query.vertexEnd());
   for (int i = old_size; i < _query.vertexEnd(); i++)
      _verticesFilter[i] = 0;
}

void AromaticityMatcher::fixAromaticBond (int edge_idx)
{
   _edgesMatching[edge_idx] = 1;
}

void AromaticityMatcher::fixAtom (int query_idx, int base_idx)
{
   _verticesFilter[query_idx] = base_idx + 1;
}

void AromaticityMatcher::unfixNeighbourAromaticBonds (int vertex_idx)
{
   _verticesFilter[vertex_idx] = 0;
   const Vertex &vertex = _query.getVertex(vertex_idx);
   for (int i = vertex.neiBegin(); i != vertex.neiEnd(); i = vertex.neiNext(i))
      _edgesMatching[vertex.neiEdge(i)] = 0;

}

bool AromaticityMatcher::isAbleToMatch ()
{
   // Check edges
   bool needCheck = false;
   for (int i = 0; i < _edgesMatching.size(); i++)
      if (_edgesMatching[i])
         needCheck = true;
   if (!needCheck)
      return true;

   // Basic algorithm 
   // Clone submolecule without ignored vertices, set bonds aromatic and then check aromaticity
   QS_DEF(Array<int>, mapping);

   mapping.clear();
   for (int v_idx = _query.vertexBegin(); v_idx < _query.vertexEnd(); v_idx = _query.vertexNext(v_idx))
   {
      if (_query_vertices_filter && _query_vertices_filter[v_idx] == _ignore_value)
         continue;
      mapping.push(v_idx);
   }

   QS_DEF(Array<int>, inv_mapping);
   _submolecule.makeSubmolecule(_query, mapping, &inv_mapping);

   // Check query atoms
   for (int v_idx = _query.vertexBegin(); 
      v_idx < _query.vertexEnd(); 
      v_idx = _query.vertexNext(v_idx))
   {
      if (_query_vertices_filter && _query_vertices_filter[v_idx] == _ignore_value)
         continue;
      const QueryAtom &qAtom = _query.getQueryAtom(v_idx);
      if (qAtom.type != 0)
      {
         // This is query atom, copy information from base molecule
         int base_idx = _verticesFilter[v_idx] - 1;
         const Atom &atom = _base.getAtom(base_idx);
         _submolecule.setAtom(inv_mapping[v_idx], atom);
      }
   }

   bool need_dearomatization = false;
   // Set edges to be aromatic
   for (int i = 0; i < _edgesMatching.size(); i++)
      if (_edgesMatching[i])
      {
         const Edge &edge = _query.getEdge(i);
         int clone_idx = _submolecule.findEdgeIndex(inv_mapping[edge.beg], inv_mapping[edge.end]);

         const QueryBond &query_bond = _query.getQueryBond(i);
         if (!query_bond.can_be_aromatic)
         {
            _submolecule.getQueryBond2(clone_idx).type = 0;
            _submolecule.setBondType(clone_idx, BOND_SINGLE);
            continue;
         }

         const Bond &bond = _query.getBond(clone_idx);
         if (bond.type != BOND_AROMATIC)
            need_dearomatization = true;
         _submolecule.setBondAromatic(clone_idx);
      }

   if (!need_dearomatization)
      return true;
   QS_DEF(DearomatizationsStorage, dearomatizations);
   Dearomatizer dearomatizer(_submolecule);
   dearomatizer.enumerateDearomatizations(dearomatizations);

   DearomatizationMatcher  dearomatizationMatcher(dearomatizations, _submolecule);
   // Check edges
   for (int i = 0; i < _edgesMatching.size(); i++)
      if (_edgesMatching[i])
      {
         const QueryBond &queryBond = _query.getQueryBond(i);
         if (queryBond.type == 0)
         {
            const Bond &bond = _query.getBond(i);
            const Edge &edge = _query.getEdge(i);
            int clone_idx = _submolecule.findEdgeIndex(inv_mapping[edge.beg], inv_mapping[edge.end]);

            if (!dearomatizationMatcher.isAbleToFixBond(clone_idx, bond.type))
               return false;
            dearomatizationMatcher.fixBond(clone_idx, bond.type);
         }
      }

   return true;
}

bool AromaticityMatcher::isAbleToFixBond (int edge_idx)
{
   const QueryBond &bond = _query.getQueryBond(edge_idx);
   return bond.can_be_aromatic;
}

void AromaticityMatcher::setQueryVertexIgnoreFilterPtr (int *query_vertices_filter, int ignore_value)
{
   _ignore_value = ignore_value;
   _query_vertices_filter = query_vertices_filter;
}
